﻿//SlippageManager.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Slippage
{
	using System;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.BusinessEntities;

	/// <summary>
	/// Менеджер проскальзывания.
	/// </summary>
	public class SlippageManager : ISlippageManager
	{
		private sealed class SlippageInfo
		{
			private readonly Order _currentOrder;

			public SlippageInfo(Order currentOrder, decimal estimatedPrice)
			{
				if (currentOrder == null)
					throw new ArgumentNullException("currentOrder");

				if (currentOrder.Type != OrderTypes.Market && estimatedPrice <= 0)
					throw new ArgumentOutOfRangeException("estimatedPrice", estimatedPrice, "Неправильное значение для ожидаемой цены.");

				_currentOrder = currentOrder;
				EstimatedPrice = estimatedPrice;
			}

			private Order _prevOrder;

			public Order PrevOrder
			{
				get { return _prevOrder; }
				set
				{
					CheckOrder(value);

					if (_prevOrder != null)
						throw new ArgumentException("Для заявки {0} уже есть предыдущая заявка {1} не может быть переписана новой {2}.".Put(_currentOrder.TransactionId, _prevOrder.TransactionId, value.TransactionId), "value");

					_prevOrder = value;
				}
			}

			private Order _nextOrder;

			public Order NextOrder
			{
				get { return _nextOrder; }
				set
				{
					if (value != null)
						CheckOrder(value);

					if (_nextOrder != null && _nextOrder.State != OrderStates.Failed)
						throw new ArgumentException("Заявка {0} уже была ранее отправлена на перерегистрацию через заявку {1}, и не может быть перерегистрирована заявкой {2}.".Put(_currentOrder.TransactionId, _nextOrder.TransactionId, value.TransactionId), "value");

					_nextOrder = value;
				}
			}

			public decimal EstimatedPrice { get; private set; }
			public decimal Slippage { get; set; }

			private void CheckOrder(Order order)
			{
				if (order == null)
					throw new ArgumentNullException("order");

				if (_currentOrder == order)
					throw new ArgumentException("Текущая и другая заявки совпадают и имеют номер {0}.".Put(_currentOrder.TransactionId), "order");

				if (_currentOrder.Security != order.Security)
					throw new ArgumentException("Инструменты {0} и {1} должны совпадать.".Put(_currentOrder.Security.Id, order.Security.Id));

				// новая заявка может быть еще не отправлена на регистрацию
				//if (order.TransactionId == 0)
				//	throw new ArgumentException("Заявка {0} еще не зарегистрирована.".Put(order), "order");
			}
		}

		private readonly SynchronizedDictionary<Order, SlippageInfo> _slippages = new SynchronizedDictionary<Order, SlippageInfo>();

		/// <summary>
		/// Создать <see cref="SlippageManager"/>.
		/// </summary>
		public SlippageManager()
		{
			CalculateNegative = true;
		}

		/// <summary>
		/// Общее значение проскальзывания по всем заявкам, зарегистрированным через <see cref="Registering(StockSharp.BusinessEntities.Order)"/>
		/// или <see cref="Registering(StockSharp.BusinessEntities.Order,decimal)"/>.
		/// </summary>
		public virtual decimal Slippage { get; private set; }

		/// <summary>
		/// Считать отрицательное проскальзывание. По-умолчанию расчет выключен.
		/// </summary>
		public bool CalculateNegative { get; set; }

		/// <summary>
		/// Зарегистрировать заявку для начала мониторинга проскальзывания. Ожидаемая цена равна <see cref="Order.Price"/>.
		/// </summary>
		/// <param name="order">Заявка, на основе которой необходимо вычислять проскальзывание.</param>
		public virtual void Registering(Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			Registering(order, order.Price);
		}

		/// <summary>
		/// Зарегистрировать заявку для начала мониторинга проскальзывания.
		/// </summary>
		/// <param name="order">Заявка, на основе которой необходимо вычислять проскальзывание.</param>
		/// <param name="estimatedPrice">Ожидаемая цена сделки.</param>
		public virtual void Registering(Order order, decimal estimatedPrice)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			lock (_slippages.SyncRoot)
			{
				//Если для заявки уже зарегистрирован учет проскальзывания, то проверяем
				//не было ли это сделано через SlippageManager.ReRegistering, 
				//данная ситуация возможна когда выполняется ручная перерегистрация заявок в котировании.
				var info = _slippages.TryGetValue(order);
				if (info != null)
				{
					if (info.PrevOrder == null)
						throw new ArgumentException("Мониторинг проскальзывания для заявки {0} уже ведется.".Put(order.Id), "order");
					else
						return;
				}

				_slippages.Add(order, new SlippageInfo(order, estimatedPrice));
			}
		}

		/// <summary>
		/// Перерегистрировать заявку <paramref name="oldOrder"/> с новой ценой из <paramref name="newOrder"/>.
		/// </summary>
		/// <param name="oldOrder">Старая заявка, которая мониторится на учет проскальзывания.</param>
		/// <param name="newOrder">Новая заявка, которая содержит обновленную ожидаемую цену по сделкам.</param>
		public virtual void ReRegistering(Order oldOrder, Order newOrder)
		{
			if (oldOrder == null)
				throw new ArgumentNullException("oldOrder");

			if (newOrder == null)
				throw new ArgumentNullException("newOrder");

			lock (_slippages.SyncRoot)
			{
				var info = GetInfo(oldOrder);
				info.NextOrder = newOrder;
				_slippages.Add(newOrder, new SlippageInfo(newOrder, newOrder.Price) { PrevOrder = oldOrder });
			}
		}

		/// <summary>
		/// Удалить расчет проскальзывания для ранее зарегистрированной заявки в случае ее ошибки при регистрации.
		/// </summary>
		/// <param name="fail">Информация об ошибке.</param>
		public void RegisterFailed(OrderFail fail)
		{
			if (fail == null)
				throw new ArgumentNullException("fail");

			lock (_slippages.SyncRoot)
			{
				var order = fail.Order;

				var info = _slippages.TryGetValue(order);

				if (info == null)
					return;

				if (info.NextOrder != null)
					throw new InvalidOperationException("Заявка {0} не прошла регистрацию, но имеет следующую заявку {1} в цепочке перерегистраций.".Put(order.TransactionId, info.NextOrder.TransactionId));

				if (info.PrevOrder != null)
					_slippages[info.PrevOrder].NextOrder = null;

				_slippages.Remove(order);
			}
		}

		/// <summary>
		/// Получить текущее проскальзывание по заявке.
		/// Если заявка все еще активна, то возвращается проскальзывание,
		/// рассчитанное на основе частично исполненных сделок.
		/// </summary>
		/// <param name="order">Заявка, по которой необходимо получить проскальзывание.</param>
		/// <returns>Значение проскальзывания.</returns>
		public virtual decimal GetSlippage(Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			if (order.Type == OrderTypes.Conditional)
			{
				return order.DerivedOrder != null ? GetSlippage(order.DerivedOrder) : 0;
			}

			var slippage = 0m;

			while (order != null)
			{
				var info = GetInfo(order);

				slippage += info.Slippage;
				order = info.NextOrder;
			}

			return slippage;
		}

		/// <summary>
		/// Получить для текущей заявки изначальную, с цены которой начался мониторинг проскальзывания.
		/// </summary>
		/// <param name="order">Текущая заявка.</param>
		/// <returns>Изначальная заявка.</returns>
		public virtual Order GetOriginalOrder(Order order)
		{
			while (true)
			{
				var info = GetInfo(order);

				if (info.PrevOrder == null)
					return order;
				else
					order = info.PrevOrder;
			}
		}

		/// <summary>
		/// Получить ожидаемую цену сделки для заявки.
		/// </summary>
		/// <param name="order">Заявка, для которой необходимо получить ожидаемую цену сделки для заявки.</param>
		/// <returns>Ожидаемая цена сделки.</returns>
		public virtual decimal GetEstimatedPrice(Order order)
		{
			return GetInfo(order).EstimatedPrice;
		}

		/// <summary>
		/// Получить проскальзывание по сделке.
		/// В качестве ожидаемой цены берется цена первоначальной заявки, которая в результате
		/// изменений цены через перерегистрацию <see cref="ReRegistering"/> привела к сделке.
		/// </summary>
		/// <param name="trade">Сделка, для которой необходимо получить проскальзывание.</param>
		/// <returns>Значение проскальзывания.</returns>
		public virtual decimal GetSlippage(MyTrade trade)
		{
			if (trade == null)
				throw new ArgumentNullException("trade");

			return trade.GetSlippage(GetEstimatedPrice(GetOriginalOrder(trade.Order)), CalculateNegative);
		}

		/// <summary>
		/// Добавить новую сделку для раcсчета проскальзывания.
		/// </summary>
		/// <param name="trade">Новая сделка.</param>
		/// <returns>Проскальзывание для сделки.</returns>
		public virtual decimal AddTrade(MyTrade trade)
		{
			if (trade == null)
				throw new ArgumentNullException("trade");

			lock (_slippages.SyncRoot)
			{
				var info = _slippages.TryGetValue(trade.Order);

				if (info == null)
					return 0;

				var slippage = GetSlippage(trade);
				info.Slippage += slippage;
				Slippage += slippage;
				return slippage;
			}
		}

		private SlippageInfo GetInfo(Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			var info = _slippages.TryGetValue(order);

			if (info == null)
				throw new ArgumentException("Для заявки {0} не была найдена информации о проскальзывании.".Put(order.Id), "order");

			return info;
		}

		/// <summary>
		/// Обнулить <see cref="ISlippageManager.Slippage"/>.
		/// </summary>
		public virtual void Reset()
		{
			lock (_slippages.SyncRoot)
			{
				_slippages.Clear();
				Slippage = 0;
			}
		}
	}
}